/*
 * hkasm - A universal algebraic user defined assembler (for microcontrollers etc....)
 * Copyright (C) 2009  Henri Koskela <henri.koskela@iki.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections;
using System.Collections.Generic;


class AsmLine{
	public long address;
	public int rownumber;
	public string code;
	public OpCode opcode;//Matched opcode
		
	public AsmLine(string code,int row){
		this.code=code;
		this.rownumber=row; //For debug
	}
}
	
class Assembler{
	private ArrayList opcodes=new ArrayList();
	private ArrayList code=new ArrayList();
	private Dictionary<string, long> equs = new Dictionary<string, long>();
	private Dictionary<string, long> labelLineNumbers = new Dictionary<string, long>();
	private Dictionary<string, long> labels = new Dictionary<string, long>(); //Eli millÃ¤ arvolla korvataan		
	private long wordSize=16; //How many bits in each program address location

	/**
	 * Reads asm file and store constant and label definitions 
	 */
	public void readAsmFile(string filename){
		System.IO.TextReader file = new System.IO.StreamReader(filename);
		int textlinenumber=0;
								
		while(true){
			string line=file.ReadLine();
			if (line==null) break;
			textlinenumber++;
			line=line.Split('#')[0].Trim();  //Index0=code index1..n=comment
			string comments="";
			if (line.Contains("#")) comments=line.Substring(line.IndexOf('#'));				
			
			if (line.StartsWith(".")||line.EndsWith(":")){  //Directive or Normal command
				if (line.StartsWith(".include")) this.readAsmFile(line.Replace(".include","").Replace("\"","").Trim());
				if (line.StartsWith(".opcode")) this.opcodes.Add(new OpCode(line.Replace(".opcode",""),comments));
				if (line.EndsWith(":")) this.labelLineNumbers.Add(line.Replace(":",""),this.code.Count);
				if (line.StartsWith(".equ")) this.equs.Add(line.Split('=')[0].Replace(".equ","").Trim(),BinOperations.string2number(line.Split('=')[1]));
				if (line.StartsWith(".wordsize")) this.wordSize=BinOperations.string2number(line.Split(' ')[1]);
			}else{
				string[] commands=line.Split(';'); //It's possible to split normal command lines with ";"
				foreach(string s in commands){
					if(s.Length>0) this.code.Add(new AsmLine(s,textlinenumber));
				}
			}
		}
		file.Close();		
	}
				
	/**
	 * Find constants and labels in string.
	 * Assumes that constant names contains alphanumeric and '_' characters
	 * If constat is not defined, then leave that out
	 * 
	 */
	private string[] findConstantsUsed(string line){
		string s="";
		foreach(char c in line){
			if ((!BinOperations.isAlphanumeric(c))&&(c!='_')) 
				s+=" ";
			else 
				s+=c;
		}
		
		string[] candidates=s.Split(" ".ToCharArray());	
		ArrayList resultAL = new ArrayList();
		foreach(string test in candidates){	
			if (test.Trim()!=""){
				if (this.labelLineNumbers.ContainsKey(test.Trim())) resultAL.Add(test.Trim());
				if (this.equs.ContainsKey(test.Trim())) resultAL.Add(test.Trim());
			}
		}
		if (resultAL.Count==0) return null;
				
		string[] result=new string[resultAL.Count];
		for(int i=0;i<resultAL.Count;i++)
			result[i]=(string)resultAL[i];
			
		return result;
	}
		
	/**
	 * Find matching opcode. Replaces constants as 0 when searching
	 * (this becouse regexp extracts parameter values)
	 */
	public OpCode findOpcodeMatch(AsmLine target){
		string[] constants=findConstantsUsed(target.code);  //Taulokossa pisimmÃ¤t ensin, ne korvataan nollilla. (jos sama alkuosa pidemmÃ¤llÃ¤ ja lyhyemmÃ¤llÃ¤ vakiolla)
		foreach (OpCode op in this.opcodes){
			string dumbcode=target.code;
			if (constants!=null){
				foreach(string s in constants)
					dumbcode=dumbcode.Replace(s,"0");
			}
			if (op.matches(dumbcode))
				return op;
		}
		return null;
	}

	/**
	 * Calculate addresses from opcode bit lengths
	 */
	private void updateAddresses(){
		long bitaddress=0;
		//Find correct opcode for each line and calculate bitsize  -> store to address
		foreach(AsmLine data in this.code){
			data.opcode=this.findOpcodeMatch(data);
			if(data.opcode==null) throw new Exception("Opcode not found for linenumber "+data.rownumber+"   "+data.code);				

			data.address=bitaddress/this.wordSize;
			if(bitaddress%this.wordSize!=0) throw new Exception("WORDSIZE ERROR. Bitsize is less than wordsize at row"+data.rownumber);

			bitaddress+=data.opcode.bits;
		}
		//Find out label line numbers and copy address value
		foreach(string key in this.labelLineNumbers.Keys){ 
			long k=this.labelLineNumbers[key];
			AsmLine l= (AsmLine)this.code[(int)k];
			this.labels[key]=l.address;
		}
	}
		
	/**
	 * Compiles AsmLines to assembly
	 * Two pass required. First calculate label addresses and then do final compile
	 */
	public ArrayList compile(){
		//--- Update Addresses
		this.updateAddresses();
			
		//----Place label and equ's in command and compile---
		ArrayList resultcode=new ArrayList();

		foreach(AsmLine line in this.code){
			string[] constnames= findConstantsUsed(line.code);
			string proceccedCode=line.code;
			if (constnames!=null){
				foreach(string s in constnames){
					if (this.labels.ContainsKey(s)) proceccedCode=proceccedCode.Replace(s,this.labels[s].ToString());
					if (this.equs.ContainsKey(s)) proceccedCode=proceccedCode.Replace(s,this.equs[s].ToString());
				}
			}
			resultcode.Add(line.opcode.compile(proceccedCode,line.address));	
		}	
		return resultcode;
	}
		
	public Assembler(string filename){
		this.readAsmFile(filename);
	}

	public Assembler(){}

		
}

